// This Pine Script® code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © PhenLabs

//@version=6
indicator(
     title            = "Liquidity Sweep Reversal & Targets [PhenLabs]",
     shorttitle       = "LSRT [PhenLabs]",
     overlay          = true,
     max_lines_count  = 500,
     max_labels_count = 500,
     max_boxes_count  = 300
)

// Only bounded timestamp lookups (pivotLen <= 50 and a 12-bar sweep window).
// Historical drawing anchors are stored timestamps, never dynamic time[offset].
max_bars_back(time, 64)

// ============================================================================
// 1. ENUMS & INPUTS (14 Inputs — Progressive Disclosure)
// ============================================================================

enum FilterMode
    AUTO_OPTIMIZE = "Auto-Optimize (Best Chart Win Rate)"
    BALANCED      = "Balanced (55% Absorb)"
    CONSERVATIVE  = "Conservative (65% Absorb)"
    AGGRESSIVE    = "Aggressive (45% Absorb)"
    TREND_ALIGNED = "Trend-Aligned (EMA 50 + 52% Absorb)"
    VOL_CLIMAX    = "Volume Climax (62% Absorb + 1.25x Vol)"
    WIDE_DEFENSE  = "Wide-Stop Defense (60% Absorb + 1.6x SL)"
    CUSTOM        = "Custom Threshold"

enum TargetMode
    HYBRID      = "R Ladder + Opposing Pool Magnet"
    R_MULTIPLES = "Fixed R Ladder (1R / 2R / 3R)"

enum DashSize
    TINY   = "Tiny"
    SMALL  = "Small"
    NORMAL = "Normal"
    LARGE  = "Large"

string GRP_POOL = "1. Liquidity Defense Zones"
string GRP_FILT = "2. Sweep Absorption & Auto-Optimizer"
string GRP_PLAN = "3. Entry, Stop Loss & TP1–TP3 Targets"
string GRP_VIS  = "4. Visuals & Dashboard"

int        pivotLen        = input.int(10, "Swing Liquidity Lookback", minval = 3, maxval = 50, group = GRP_POOL, tooltip = "Confirmed left/right bars used to detect Buy-Side (BSL) and Sell-Side (SSL) swing liquidity pools. Pools rest in advance so sweeps trigger on bar close without right-pivot delay.")
int        maxPoolsSide    = input.int(2, "Active Pools Shown Per Side", minval = 1, maxval = 5, group = GRP_POOL, tooltip = "Maximum number of unmitigated Buy-Side (above price) and Sell-Side (below price) liquidity defense zones displayed at once.")
float      zoneWidthAtr    = input.float(0.35, "Defense Zone Thickness (ATR)", minval = 0.10, maxval = 1.20, step = 0.05, group = GRP_POOL, tooltip = "Volatility-scaled thickness of each BSL and SSL liquidity defense zone in ATR(14) units.")

FilterMode filterPreset    = input.enum(FilterMode.AUTO_OPTIMIZE, "Absorption Filter Mode", group = GRP_FILT, tooltip = "Auto-Optimize runs all 6 built-in strategy profiles in parallel across the current asset and timeframe, then selects and renders the exact profile with the highest closed-bar TP1+ Win Rate.")
float      customMinAbsorb = input.float(55.0, "Custom Min Absorption (%)", minval = 25.0, maxval = 90.0, step = 5.0, group = GRP_FILT, active = filterPreset == FilterMode.CUSTOM, tooltip = "Minimum Buyer/Seller Absorption score (0–100%) required when Absorption Filter Mode is set to Custom Threshold.")

TargetMode targetMode      = input.enum(TargetMode.HYBRID, "Target Projection Mode", group = GRP_PLAN, tooltip = "Fixed R Ladder projects TP1/TP2/TP3 at 1.0R, 2.0R, and 3.0R. R Ladder + Opposing Pool Magnet snaps TP3 to the nearest unbroken opposing liquidity pool when it offers a clean structural target.")
float      slBufferAtr     = input.float(0.20, "Stop Loss Buffer Beyond Wick (ATR)", minval = 0.05, maxval = 1.00, step = 0.05, group = GRP_PLAN, tooltip = "Base ATR(14) breathing room placed beyond the sweep wick extreme for the Stop Loss level.")
bool       useBeRatchet    = input.bool(true, "Move Stop to Breakeven After TP1", group = GRP_PLAN, tooltip = "When enabled, once TP1 is reached on a confirmed bar, the active Stop Loss ratchets to the Entry price (Breakeven) for remaining targets.")

bool       showPastPlans   = input.bool(false, "Keep Historical Trade Plans (Last 5)", group = GRP_VIS, tooltip = "Off by default so only the current/latest trade plan stays on price. Enable to keep the last 5 trade plans of the active profile visible on the chart.")
bool       showSkipped     = input.bool(false, "Show Filtered (SKIP) Sweeps", group = GRP_VIS, tooltip = "Displays small muted markers when a liquidity sweep occurs but fails the active profile's filter.")
bool       showDash        = input.bool(true, "Show Summary Dashboard", group = GRP_VIS, tooltip = "Displays the live Liquidity Sweep, Absorption %, Auto-Optimizer Winner, Trade Plan, and Win/Loss performance table in the top-right corner.")
DashSize   dashSizeInput   = input.enum(DashSize.SMALL, "Table Size", group = GRP_VIS, active = showDash, tooltip = "Scales text size inside the summary dashboard.")
color      bullCol         = input.color(#00E6A8, "Bullish Reversal & Reward Color", group = GRP_VIS, tooltip = "Accent color for Sell-Side Liquidity (SSL) defense zones, BUY callouts, and Take-Profit zones.")
color      bearCol         = input.color(#FF3B69, "Bearish Reversal & Risk Color", group = GRP_VIS, tooltip = "Accent color for Buy-Side Liquidity (BSL) defense zones, SELL callouts, and Stop-Loss zones.")

// ============================================================================
// 2. USER-DEFINED TYPES (UDTs) & METHODS
// ============================================================================

type LiqPool
    int   side       // 1 = BSL (Swing High Resistance), -1 = SSL (Swing Low Support)
    float level      // Exact swing pivot price
    float outerBound // Outer defense boundary
    float innerBound // Inner boundary for visual zone box
    int   pivotTime  // Opening timestamp captured when the pivot is confirmed
    float defPct     // Buyer/Seller defense absorption % (0-100)
    box   zoneBox
    line  lvlLine
    label zoneLbl

method clearPoolDrawings(LiqPool this) =>
    if not na(this.zoneBox)
        box.delete(this.zoneBox)
        this.zoneBox := na
    if not na(this.lvlLine)
        line.delete(this.lvlLine)
        this.lvlLine := na
    if not na(this.zoneLbl)
        label.delete(this.zoneLbl)
        this.zoneLbl := na

// Stores pure coordinates/states of a trade plan for a specific profile (0..6)
type SavedPlan
    int   profId
    int   dir
    int   entryBar
    float entry
    float initSl
    float activeSl
    float risk
    float tp1
    float tp2
    float tp3
    float tp1R
    float tp2R
    float tp3R
    bool  tp3IsMag
    float absorbPct
    bool  tp1Hit
    bool  tp2Hit
    bool  tp3Hit
    bool  slHit
    bool  beHit
    int   status // 0 = ACTIVE, 1 = TP3 FULL WIN, 2 = PARTIAL WIN (TP1+), -1 = LOSS (SL), 3 = EXPIRED/FLIP
    int   endTime
    int   swLeftTime
    float swTop
    float swBot
    int   entryTime
    float signalY    // Stored price anchor; avoids abovebar/belowbar history lookups

// Self-contained profile engine running across the full chart history
type ProfileEngine
    int    id
    string nameTag
    float  minAbsorb
    float  maxSweep
    float  slScale
    float  minRiskAtr
    bool   requireTrend
    float  minRelAct
    int    totalTrades
    int    winTrades
    int    lossTrades
    int    tp1Count
    int    tp2Count
    int    tp3Count
    float  cumNetR
    int    lastSigDir
    float  lastSigAbsorb
    string lastSigText
    int    activePlanIdx // Index in global allPlans array (-1 if none)
    bool   barBullTake
    bool   barBearTake
    bool   barBullSkip
    bool   barBearSkip
    bool   barTp1Evt
    bool   barTp2Evt
    bool   barTp3Evt
    bool   barSlEvt

// ============================================================================
// 3. CORE METRICS: VOLATILITY, TREND & TRUE BUYER/SELLER ABSORPTION
// ============================================================================

f_clamp(float val, float lo, float hi) =>
    math.min(math.max(val, lo), hi)

float rawAtr      = ta.atr(14)
float safeAtr     = nz(rawAtr, math.max(high - low, syminfo.mintick * 10.0))
float barSpan     = math.max(high - low, syminfo.mintick)
float emaTrend    = ta.ema(close, 50)
bool  bullTrendOk = close >= nz(emaTrend, close)
bool  bearTrendOk = close <= nz(emaTrend, close)

float volSma      = ta.sma(volume, 20)
bool  hasRealVol  = not na(volume) and volume > 0 and not na(volSma) and volSma > 0
float relActivity = hasRealVol ? (volume / volSma) : (barSpan / math.max(safeAtr, syminfo.mintick))
float actNorm     = f_clamp(relActivity / 2.0, 0.0, 1.0)

float bullCloseShare  = f_clamp((close - low) / barSpan, 0.0, 1.0)
float bearCloseShare  = f_clamp((high - close) / barSpan, 0.0, 1.0)
float rawBarDelta     = ((2.0 * bullCloseShare) - 1.0) * f_clamp(relActivity, 0.3, 2.5)
float netDeltaPct     = f_clamp(ta.ema(rawBarDelta, 5) * 60.0, -100.0, 100.0)

float bullBodyBonus   = close >= open ? 1.0 : 0.65
float bearBodyBonus   = close <= open ? 1.0 : 0.65
float buyerAbsorbVal  = math.round(f_clamp((0.50 * bullCloseShare + 0.35 * actNorm + 0.15 * bullBodyBonus) * 100.0, 0.0, 100.0), 1)
float sellerAbsorbVal = math.round(f_clamp((0.50 * bearCloseShare + 0.35 * actNorm + 0.15 * bearBodyBonus) * 100.0, 0.0, 100.0), 1)

// ============================================================================
// 4. PERSISTENT REGISTRIES & 7 PARALLEL PROFILE ENGINES
// ============================================================================

var array<LiqPool>       bslPools   = array.new<LiqPool>()
var array<LiqPool>       sslPools   = array.new<LiqPool>()
var array<ProfileEngine> engines    = array.new<ProfileEngine>()
var array<SavedPlan>     allPlans   = array.new<SavedPlan>()
var array<box>           drawnBoxes = array.new<box>()
var array<line>          drawnLines = array.new<line>()
var array<label>         drawnLbls  = array.new<label>()

if barstate.isfirst
    // 0: Balanced, 1: Conservative, 2: Aggressive, 3: Trend-Aligned, 4: Volume Climax, 5: Wide-Stop Defense, 6: Custom
    array.push(engines, ProfileEngine.new(0, "Balanced (55% Absorb)",       55.0, 1.50, 1.00, 0.35, false, 0.00, 0, 0, 0, 0, 0, 0, 0.0, 0, 0.0, "NONE", -1, false, false, false, false, false, false, false, false))
    array.push(engines, ProfileEngine.new(1, "Conservative (65% Absorb)",   65.0, 1.25, 1.15, 0.35, false, 0.00, 0, 0, 0, 0, 0, 0, 0.0, 0, 0.0, "NONE", -1, false, false, false, false, false, false, false, false))
    array.push(engines, ProfileEngine.new(2, "Aggressive (45% Absorb)",     45.0, 2.00, 0.85, 0.30, false, 0.00, 0, 0, 0, 0, 0, 0, 0.0, 0, 0.0, "NONE", -1, false, false, false, false, false, false, false, false))
    array.push(engines, ProfileEngine.new(3, "Trend-Aligned (EMA 50)",      52.0, 1.60, 0.90, 0.30, true,  0.00, 0, 0, 0, 0, 0, 0, 0.0, 0, 0.0, "NONE", -1, false, false, false, false, false, false, false, false))
    array.push(engines, ProfileEngine.new(4, "Volume Climax (62% + RVOL)",  62.0, 1.40, 0.80, 0.28, false, 1.25, 0, 0, 0, 0, 0, 0, 0.0, 0, 0.0, "NONE", -1, false, false, false, false, false, false, false, false))
    array.push(engines, ProfileEngine.new(5, "Wide-Stop Defense (1.6x SL)", 60.0, 1.50, 1.60, 0.45, false, 0.00, 0, 0, 0, 0, 0, 0, 0.0, 0, 0.0, "NONE", -1, false, false, false, false, false, false, false, false))
    array.push(engines, ProfileEngine.new(6, "Custom Threshold",            customMinAbsorb, 1.60, 1.00, 0.35, false, 0.00, 0, 0, 0, 0, 0, 0, 0.0, 0, 0.0, "NONE", -1, false, false, false, false, false, false, false, false))

// Keep Custom Threshold synced if user changes customMinAbsorb
if array.size(engines) == 7
    ProfileEngine custEng = array.get(engines, 6)
    custEng.minAbsorb := customMinAbsorb

float ph = ta.pivothigh(high, pivotLen, pivotLen)
float pl = ta.pivotlow(low, pivotLen, pivotLen)
float phDefScore = sellerAbsorbVal[pivotLen]
float plDefScore = buyerAbsorbVal[pivotLen]
int   pivotTime = time[pivotLen]
int   sweepWindowTime = nz(time[12], time)

float fiveBarMoveAtr = (close - nz(close[5], close)) / math.max(safeAtr, syminfo.mintick)
bool  bearCrashGuard = fiveBarMoveAtr < -3.6 and close < open
bool  bullCrashGuard = fiveBarMoveAtr >  3.6 and close > open

// ============================================================================
// 5. DETERMINISTIC CLOSED-BAR STATE MACHINE (ALL 7 ENGINES IN PARALLEL)
// ============================================================================

if barstate.isconfirmed and array.size(engines) == 7
    // ------------------------------------------------------------------------
    // STEP A: Update & Resolve Active Trade Plan in Each of the 7 Engines
    // ------------------------------------------------------------------------
    for eIdx = 0 to 6
        ProfileEngine eng = array.get(engines, eIdx)
        eng.barBullTake := false
        eng.barBearTake := false
        eng.barBullSkip := false
        eng.barBearSkip := false
        eng.barTp1Evt   := false
        eng.barTp2Evt   := false
        eng.barTp3Evt   := false
        eng.barSlEvt    := false

        if eng.activePlanIdx >= 0 and eng.activePlanIdx < array.size(allPlans)
            SavedPlan cp = array.get(allPlans, eng.activePlanIdx)
            if cp.status == 0 and bar_index > cp.entryBar
                if cp.dir == 1
                    if low <= cp.activeSl
                        if cp.tp1Hit
                            cp.beHit       := true
                            cp.status      := 2
                            cp.endTime     := time
                            eng.totalTrades += 1
                            eng.winTrades   += 1
                            float bankedR  = cp.tp2Hit ? ((cp.tp1R + cp.tp2R) * 0.5) : (cp.tp1R * 0.5)
                            eng.cumNetR    += bankedR
                        else
                            cp.slHit       := true
                            cp.status      := -1
                            cp.endTime     := time
                            eng.totalTrades += 1
                            eng.lossTrades  += 1
                            eng.cumNetR    -= 1.0
                            eng.barSlEvt   := true
                    else
                        if not cp.tp1Hit and high >= cp.tp1
                            cp.tp1Hit     := true
                            eng.tp1Count  += 1
                            eng.barTp1Evt := true
                            if useBeRatchet
                                cp.activeSl := cp.entry
                        if not cp.tp2Hit and high >= cp.tp2
                            cp.tp2Hit     := true
                            eng.tp2Count  += 1
                            eng.barTp2Evt := true
                        if not cp.tp3Hit and high >= cp.tp3
                            cp.tp3Hit       := true
                            eng.tp3Count    += 1
                            eng.barTp3Evt   := true
                            cp.status       := 1
                            cp.endTime      := time
                            eng.totalTrades += 1
                            eng.winTrades   += 1
                            eng.cumNetR     += cp.tp3R
                        else if (bar_index - cp.entryBar) >= 60
                            cp.status       := cp.tp1Hit ? 2 : 3
                            cp.endTime      := time
                            eng.totalTrades += 1
                            if cp.tp1Hit
                                eng.winTrades += 1
                                eng.cumNetR   += cp.tp1R
                            else
                                float expR = (close - cp.entry) / math.max(cp.risk, syminfo.mintick)
                                if expR >= 0.0
                                    eng.winTrades  += 1
                                else
                                    eng.lossTrades += 1
                                eng.cumNetR += expR
                else if cp.dir == -1
                    if high >= cp.activeSl
                        if cp.tp1Hit
                            cp.beHit       := true
                            cp.status      := 2
                            cp.endTime     := time
                            eng.totalTrades += 1
                            eng.winTrades   += 1
                            float bankedR  = cp.tp2Hit ? ((cp.tp1R + cp.tp2R) * 0.5) : (cp.tp1R * 0.5)
                            eng.cumNetR    += bankedR
                        else
                            cp.slHit       := true
                            cp.status      := -1
                            cp.endTime     := time
                            eng.totalTrades += 1
                            eng.lossTrades  += 1
                            eng.cumNetR    -= 1.0
                            eng.barSlEvt   := true
                    else
                        if not cp.tp1Hit and low <= cp.tp1
                            cp.tp1Hit     := true
                            eng.tp1Count  += 1
                            eng.barTp1Evt := true
                            if useBeRatchet
                                cp.activeSl := cp.entry
                        if not cp.tp2Hit and low <= cp.tp2
                            cp.tp2Hit     := true
                            eng.tp2Count  += 1
                            eng.barTp2Evt := true
                        if not cp.tp3Hit and low <= cp.tp3
                            cp.tp3Hit       := true
                            eng.tp3Count    += 1
                            eng.barTp3Evt   := true
                            cp.status       := 1
                            cp.endTime      := time
                            eng.totalTrades += 1
                            eng.winTrades   += 1
                            eng.cumNetR     += cp.tp3R
                        else if (bar_index - cp.entryBar) >= 60
                            cp.status       := cp.tp1Hit ? 2 : 3
                            cp.endTime      := time
                            eng.totalTrades += 1
                            if cp.tp1Hit
                                eng.winTrades += 1
                                eng.cumNetR   += cp.tp1R
                            else
                                float expR = (cp.entry - close) / math.max(cp.risk, syminfo.mintick)
                                if expR >= 0.0
                                    eng.winTrades  += 1
                                else
                                    eng.lossTrades += 1
                                eng.cumNetR += expR

    // ------------------------------------------------------------------------
    // STEP B: Check Armed SSL & BSL Pools for Sweeps vs. Breakdowns
    // ------------------------------------------------------------------------
    bool  bullCand          = false
    float sweptSslLvl       = na
    float sweptSslOuter     = na
    int   sweptSslTime      = time
    float bullSweepDepthAtr = 0.0

    if array.size(sslPools) > 0
        for i = array.size(sslPools) - 1 to 0
            LiqPool p = array.get(sslPools, i)
            if close < p.outerBound or low < (p.level - 2.00 * safeAtr)
                p.clearPoolDrawings()
                array.remove(sslPools, i)
            else if low < p.level and close >= p.level
                bullCand := true
                if na(sweptSslLvl) or p.level < sweptSslLvl
                    sweptSslLvl       := p.level
                    sweptSslOuter     := p.outerBound
                    sweptSslTime      := p.pivotTime
                    bullSweepDepthAtr := (p.level - low) / math.max(safeAtr, syminfo.mintick)
                p.clearPoolDrawings()
                array.remove(sslPools, i)

    bool  bearCand          = false
    float sweptBslLvl       = na
    float sweptBslOuter     = na
    int   sweptBslTime      = time
    float bearSweepDepthAtr = 0.0

    if array.size(bslPools) > 0
        for i = array.size(bslPools) - 1 to 0
            LiqPool p = array.get(bslPools, i)
            if close > p.outerBound or high > (p.level + 2.00 * safeAtr)
                p.clearPoolDrawings()
                array.remove(bslPools, i)
            else if high > p.level and close <= p.level
                bearCand := true
                if na(sweptBslLvl) or p.level > sweptBslLvl
                    sweptBslLvl       := p.level
                    sweptBslOuter     := p.outerBound
                    sweptBslTime      := p.pivotTime
                    bearSweepDepthAtr := (high - p.level) / math.max(safeAtr, syminfo.mintick)
                p.clearPoolDrawings()
                array.remove(bslPools, i)

    if bullCand and bearCand
        bullCand := false
        bearCand := false

    // ------------------------------------------------------------------------
    // STEP C: Register Newly Confirmed Swing Pools (If Unbroken Since Pivot)
    // ------------------------------------------------------------------------
    int pBar = bar_index - pivotLen
    if not na(ph) and pBar >= 0
        float zThick = safeAtr * zoneWidthAtr
        float outerH = ph + zThick * 0.65
        float innerH = ph - zThick * 0.35
        bool  intact = true
        for k = 0 to pivotLen - 1
            if close[k] > outerH
                intact := false
        if intact
            LiqPool newBsl = LiqPool.new(1, ph, outerH, innerH, pivotTime, nz(phDefScore, 55.0), na, na, na)
            array.push(bslPools, newBsl)
            while array.size(bslPools) > 8
                LiqPool oldP = array.shift(bslPools)
                oldP.clearPoolDrawings()

    if not na(pl) and pBar >= 0
        float zThick = safeAtr * zoneWidthAtr
        float outerL = pl - zThick * 0.65
        float innerL = pl + zThick * 0.35
        bool  intact = true
        for k = 0 to pivotLen - 1
            if close[k] < outerL
                intact := false
        if intact
            LiqPool newSsl = LiqPool.new(-1, pl, outerL, innerL, pivotTime, nz(plDefScore, 55.0), na, na, na)
            array.push(sslPools, newSsl)
            while array.size(sslPools) > 8
                LiqPool oldP = array.shift(sslPools)
                oldP.clearPoolDrawings()

    // ------------------------------------------------------------------------
    // STEP D: Evaluate Candidate Sweep Across All 7 Engines Independently
    // ------------------------------------------------------------------------
    if bullCand or bearCand
        for eIdx = 0 to 6
            ProfileEngine eng = array.get(engines, eIdx)
            bool trendPassBull = not eng.requireTrend or bullTrendOk
            bool trendPassBear = not eng.requireTrend or bearTrendOk
            bool actPass       = relActivity >= eng.minRelAct

            if bullCand
                if bullSweepDepthAtr <= eng.maxSweep and buyerAbsorbVal >= eng.minAbsorb and not bearCrashGuard and trendPassBull and actPass
                    eng.barBullTake   := true
                    eng.lastSigDir    := 1
                    eng.lastSigAbsorb := buyerAbsorbVal
                    eng.lastSigText   := "BUY (SSL · " + str.tostring(buyerAbsorbVal, "#") + "%)"
                else
                    eng.barBullSkip   := true
                    eng.lastSigText   := "SKIP (SSL · " + str.tostring(buyerAbsorbVal, "#") + "%)"

            if bearCand
                if bearSweepDepthAtr <= eng.maxSweep and sellerAbsorbVal >= eng.minAbsorb and not bullCrashGuard and trendPassBear and actPass
                    eng.barBearTake   := true
                    eng.lastSigDir    := -1
                    eng.lastSigAbsorb := sellerAbsorbVal
                    eng.lastSigText   := "SELL (BSL · " + str.tostring(sellerAbsorbVal, "#") + "%)"
                else
                    eng.barBearSkip   := true
                    eng.lastSigText   := "SKIP (BSL · " + str.tostring(sellerAbsorbVal, "#") + "%)"

            if eng.barBullTake or eng.barBearTake
                // Resolve any still-open plan in this engine before opening the new one
                if eng.activePlanIdx >= 0 and eng.activePlanIdx < array.size(allPlans)
                    SavedPlan prevP = array.get(allPlans, eng.activePlanIdx)
                    if prevP.status == 0
                        prevP.status    := prevP.tp1Hit ? 2 : 3
                        prevP.endTime   := time
                        eng.totalTrades += 1
                        if prevP.tp1Hit
                            eng.winTrades += 1
                            eng.cumNetR   += prevP.tp1R
                        else
                            float flipR = prevP.dir == 1 ? (close - prevP.entry) / math.max(prevP.risk, syminfo.mintick) : (prevP.entry - close) / math.max(prevP.risk, syminfo.mintick)
                            if flipR >= 0.0
                                eng.winTrades  += 1
                            else
                                eng.lossTrades += 1
                            eng.cumNetR += flipR

                int   sDir   = eng.barBullTake ? 1 : -1
                float entryP = close
                float effSlB = slBufferAtr * eng.slScale
                float rawSl  = sDir == 1 ? (math.min(low, nz(sweptSslOuter, low)) - effSlB * safeAtr) : (math.max(high, nz(sweptBslOuter, high)) + effSlB * safeAtr)
                float clampedRisk = f_clamp(math.abs(entryP - rawSl), safeAtr * eng.minRiskAtr, safeAtr * 3.0)
                float finalSl = sDir == 1 ? (entryP - clampedRisk) : (entryP + clampedRisk)

                float tp1P = sDir == 1 ? (entryP + 1.0 * clampedRisk) : (entryP - 1.0 * clampedRisk)
                float tp2P = sDir == 1 ? (entryP + 2.0 * clampedRisk) : (entryP - 2.0 * clampedRisk)
                float tp3P = sDir == 1 ? (entryP + 3.0 * clampedRisk) : (entryP - 3.0 * clampedRisk)
                float tp3RVal = 3.0
                bool  isMag   = false

                if targetMode == TargetMode.HYBRID
                    if sDir == 1 and array.size(bslPools) > 0
                        float bestMag = na
                        for i = 0 to array.size(bslPools) - 1
                            LiqPool opp = array.get(bslPools, i)
                            float distR = (opp.level - entryP) / clampedRisk
                            if distR >= 2.2 and distR <= 4.5
                                if na(bestMag) or opp.level < bestMag
                                    bestMag := opp.level
                        if not na(bestMag)
                            tp3P    := bestMag
                            tp3RVal := math.round((bestMag - entryP) / clampedRisk, 1)
                            isMag   := true
                    else if sDir == -1 and array.size(sslPools) > 0
                        float bestMag = na
                        for i = 0 to array.size(sslPools) - 1
                            LiqPool opp = array.get(sslPools, i)
                            float distR = (entryP - opp.level) / clampedRisk
                            if distR >= 2.2 and distR <= 4.5
                                if na(bestMag) or opp.level > bestMag
                                    bestMag := opp.level
                        if not na(bestMag)
                            tp3P    := bestMag
                            tp3RVal := math.round((entryP - bestMag) / clampedRisk, 1)
                            isMag   := true

                int   swLeftTime = math.max(sweepWindowTime, sDir == 1 ? sweptSslTime : sweptBslTime)
                float swTop  = sDir == 1 ? sweptSslLvl : math.max(high, nz(sweptBslOuter, high))
                float swBot  = sDir == 1 ? math.min(low, nz(sweptSslOuter, low)) : sweptBslLvl
                float absVal = sDir == 1 ? buyerAbsorbVal : sellerAbsorbVal

                SavedPlan np = SavedPlan.new(
                     eIdx, sDir, bar_index, entryP, finalSl, finalSl, clampedRisk,
                     tp1P, tp2P, tp3P, 1.0, 2.0, tp3RVal, isMag, absVal,
                     false, false, false, false, false, 0, time,
                     swLeftTime, swTop, swBot, time,
                     sDir == 1 ? low - safeAtr * 0.15 : high + safeAtr * 0.15
                )
                array.push(allPlans, np)
                eng.activePlanIdx := array.size(allPlans) - 1

                // Keep allPlans bounded per profile (max 8 saved plans per profile) without invalidating activePlanIdx
                int profPlanCount = 0
                int oldestIdx     = -1
                for k = 0 to array.size(allPlans) - 1
                    SavedPlan spk = array.get(allPlans, k)
                    if spk.profId == eIdx
                        profPlanCount += 1
                        if oldestIdx == -1
                            oldestIdx := k
                if profPlanCount > 8 and oldestIdx >= 0
                    array.remove(allPlans, oldestIdx)
                    // Shift activePlanIdx down by 1 for any engine pointing after oldestIdx
                    for m = 0 to 6
                        ProfileEngine em = array.get(engines, m)
                        if em.activePlanIdx > oldestIdx
                            em.activePlanIdx -= 1

// ============================================================================
// 6. SELECT ACTIVE / HIGHEST-WINRATE ENGINE & RENDER VISUALS
// ============================================================================

int selectedEngIdx = 0
if array.size(engines) == 7
    if filterPreset == FilterMode.AUTO_OPTIMIZE
        // Pick the built-in profile (0..5) with the strictly highest actual TP1+ Win Rate on this chart
        float bestWr   = -1.0
        float bestNetR = -1e9
        int   bestId   = 0
        // First check profiles with at least 3 closed trades so a 1-trade sample doesn't distort when history is rich
        for eIdx = 0 to 5
            ProfileEngine eng = array.get(engines, eIdx)
            if eng.totalTrades >= 3
                float wr = (float(eng.winTrades) / float(eng.totalTrades)) * 100.0
                if wr > bestWr or (wr == bestWr and eng.cumNetR > bestNetR)
                    bestWr   := wr
                    bestNetR := eng.cumNetR
                    bestId   := eIdx
        // Also guarantee Auto-Optimize is NEVER lower than Balanced (0), Conservative (1), or Aggressive (2)
        for coreIdx = 0 to 2
            ProfileEngine cEng = array.get(engines, coreIdx)
            if cEng.totalTrades > 0
                float cWr = (float(cEng.winTrades) / float(cEng.totalTrades)) * 100.0
                if cWr > bestWr or (cWr == bestWr and cEng.cumNetR > bestNetR)
                    bestWr   := cWr
                    bestNetR := cEng.cumNetR
                    bestId   := coreIdx
        // Fallback if chart has very few bars (<3 trades on all profiles)
        if bestWr < 0.0
            for eIdx = 0 to 5
                ProfileEngine eng = array.get(engines, eIdx)
                if eng.totalTrades > 0
                    float wr = (float(eng.winTrades) / float(eng.totalTrades)) * 100.0
                    if wr > bestWr or (wr == bestWr and eng.cumNetR > bestNetR)
                        bestWr   := wr
                        bestNetR := eng.cumNetR
                        bestId   := eIdx
        selectedEngIdx := bestId
    else
        selectedEngIdx := switch filterPreset
            FilterMode.BALANCED      => 0
            FilterMode.CONSERVATIVE  => 1
            FilterMode.AGGRESSIVE    => 2
            FilterMode.TREND_ALIGNED => 3
            FilterMode.VOL_CLIMAX    => 4
            FilterMode.WIDE_DEFENSE  => 5
            FilterMode.CUSTOM        => 6
            => 0

float nearestBslPrice = na
float nearestBslDef   = na
float nearestSslPrice = na
float nearestSslDef   = na

if barstate.islast and array.size(engines) == 7
    // Clear previous last-bar drawings so nothing duplicates on realtime ticks
    if array.size(drawnBoxes) > 0
        for i = 0 to array.size(drawnBoxes) - 1
            box.delete(array.get(drawnBoxes, i))
        array.clear(drawnBoxes)
    if array.size(drawnLines) > 0
        for i = 0 to array.size(drawnLines) - 1
            line.delete(array.get(drawnLines, i))
        array.clear(drawnLines)
    if array.size(drawnLbls) > 0
        for i = 0 to array.size(drawnLbls) - 1
            label.delete(array.get(drawnLbls, i))
        array.clear(drawnLbls)

    // Collect plans belonging to selectedEngIdx
    array<int> selPlanIndices = array.new<int>()
    if array.size(allPlans) > 0
        for k = 0 to array.size(allPlans) - 1
            SavedPlan sp = array.get(allPlans, k)
            if sp.profId == selectedEngIdx
                array.push(selPlanIndices, k)

    // Every drawing uses stored timestamps. No bar-index conversion or deep
    // historical series lookup is needed when a new realtime bar arrives.
    int barSpanMs = math.max(1, nz(time_close - time, 60000))
    int projectedRightTime = nz(time("", bars_back = -6), time + 6 * barSpanMs)

    // Draw signal badges & swept zones for the last 8 setups of the active/winning profile
    int totalSelPlans = array.size(selPlanIndices)
    if totalSelPlans > 0
        int startSig = math.max(0, totalSelPlans - 8)
        int startPln = showPastPlans ? math.max(0, totalSelPlans - 5) : (totalSelPlans - 1)
        for idxPos = startSig to totalSelPlans - 1
            SavedPlan sp = array.get(allPlans, array.get(selPlanIndices, idxPos))
            if not na(sp.entryTime) and not na(sp.swLeftTime)
                int   leftTime = sp.swLeftTime
                color swCol    = sp.dir == 1 ? bullCol : bearCol
                box swBox = box.new(
                     left         = leftTime,
                     top          = sp.swTop,
                     right        = sp.entryTime,
                     bottom       = sp.swBot,
                     xloc         = xloc.bar_time,
                     border_color = color.new(swCol, 20),
                     border_width = 1,
                     border_style = line.style_dashed,
                     bgcolor      = color.new(swCol, 82)
                )
                array.push(drawnBoxes, swBox)

                string sigTag = (sp.dir == 1 ? "BUY · " : "SELL · ") + str.tostring(sp.absorbPct, "#") + "%"
                label sLbl = label.new(
                     x               = sp.entryTime,
                     y               = sp.signalY,
                     text            = sigTag,
                     xloc            = xloc.bar_time,
                     yloc            = yloc.price,
                     color           = swCol,
                     style           = sp.dir == 1 ? label.style_label_up : label.style_label_down,
                     textcolor       = #0B0E14,
                     size            = size.small,
                     text_formatting = text.format_bold,
                     tooltip         = (sp.dir == 1 ? "Bullish SSL Sweep Reversal\nBuyer Absorption: " : "Bearish BSL Sweep Reversal\nSeller Absorption: ") + str.tostring(sp.absorbPct, "#.1") + "%"
                )
                array.push(drawnLbls, sLbl)

        // Draw TradePlan boxes, lines, and labels for the active/winning profile
        for idxPos = startPln to totalSelPlans - 1
            SavedPlan sp = array.get(allPlans, array.get(selPlanIndices, idxPos))
            int rightTime = sp.status == 0 ? projectedRightTime : sp.endTime
            if not na(sp.entryTime) and not na(rightTime)
                int x1Time = sp.entryTime
                int x2Time = math.max(rightTime, x1Time + 1)

                box rskBox = box.new(x1Time, math.max(sp.entry, sp.initSl), x2Time, math.min(sp.entry, sp.initSl), xloc = xloc.bar_time, border_color = color.new(bearCol, 60), border_width = 1, bgcolor = color.new(bearCol, 88))
                box rwdBox = box.new(x1Time, math.max(sp.entry, sp.tp3), x2Time, math.min(sp.entry, sp.tp3), xloc = xloc.bar_time, border_color = color.new(bullCol, 60), border_width = 1, bgcolor = color.new(bullCol, 90))
                array.push(drawnBoxes, rskBox)
                array.push(drawnBoxes, rwdBox)

                line eLn  = line.new(x1Time, sp.entry, x2Time, sp.entry, xloc = xloc.bar_time, color = color.new(chart.fg_color, 20), width = 2, style = line.style_solid)
                line sLn  = line.new(x1Time, sp.initSl, x2Time, sp.initSl, xloc = xloc.bar_time, color = color.new(bearCol, 15), width = 2, style = line.style_solid)
                line t1Ln = line.new(x1Time, sp.tp1, x2Time, sp.tp1, xloc = xloc.bar_time, color = color.new(bullCol, 25), width = 1, style = line.style_dotted)
                line t2Ln = line.new(x1Time, sp.tp2, x2Time, sp.tp2, xloc = xloc.bar_time, color = color.new(bullCol, 20), width = 1, style = line.style_dashed)
                line t3Ln = line.new(x1Time, sp.tp3, x2Time, sp.tp3, xloc = xloc.bar_time, color = color.new(bullCol, 10), width = 2, style = line.style_solid)
                array.push(drawnLines, eLn)
                array.push(drawnLines, sLn)
                array.push(drawnLines, t1Ln)
                array.push(drawnLines, t2Ln)
                array.push(drawnLines, t3Ln)

                string pFmt     = format.mintick
                string entryTxt = "ENTRY · " + str.tostring(sp.entry, pFmt)
                string slState  = sp.slHit ? " ✗" : sp.beHit ? " (BE ✓)" : (sp.tp1Hit and sp.activeSl == sp.entry) ? " (BE ARMED)" : " (-1.0R)"
                string slTxt    = "SL · " + str.tostring(sp.initSl, pFmt) + slState
                string tp1Txt   = "TP1 · " + str.tostring(sp.tp1, pFmt) + " (" + str.tostring(sp.tp1R, "#.#") + "R)" + (sp.tp1Hit ? " ✓" : "")
                string tp2Txt   = "TP2 · " + str.tostring(sp.tp2, pFmt) + " (" + str.tostring(sp.tp2R, "#.#") + "R)" + (sp.tp2Hit ? " ✓" : "")
                string magTag   = sp.tp3IsMag ? " · MAG" : ""
                string tp3Txt   = "TP3 · " + str.tostring(sp.tp3, pFmt) + " (" + str.tostring(sp.tp3R, "#.#") + "R" + magTag + ")" + (sp.tp3Hit ? " ✓" : "")

                label eLb  = label.new(x2Time, sp.entry,  entryTxt, xloc = xloc.bar_time, yloc = yloc.price, color = color.new(chart.bg_color, 25), style = label.style_label_left, textcolor = chart.fg_color, size = size.tiny, text_formatting = text.format_bold)
                label sLb  = label.new(x2Time, sp.initSl, slTxt,    xloc = xloc.bar_time, yloc = yloc.price, color = color.new(bearCol, sp.slHit ? 15 : 80), style = label.style_label_left, textcolor = chart.fg_color, size = size.tiny, text_formatting = text.format_bold)
                label t1Lb = label.new(x2Time, sp.tp1,    tp1Txt,   xloc = xloc.bar_time, yloc = yloc.price, color = color.new(bullCol, sp.tp1Hit ? 15 : 82), style = label.style_label_left, textcolor = chart.fg_color, size = size.tiny, text_formatting = text.format_bold)
                label t2Lb = label.new(x2Time, sp.tp2,    tp2Txt,   xloc = xloc.bar_time, yloc = yloc.price, color = color.new(bullCol, sp.tp2Hit ? 15 : 82), style = label.style_label_left, textcolor = chart.fg_color, size = size.tiny, text_formatting = text.format_bold)
                label t3Lb = label.new(x2Time, sp.tp3,    tp3Txt,   xloc = xloc.bar_time, yloc = yloc.price, color = color.new(bullCol, sp.tp3Hit ? 10 : 78), style = label.style_label_left, textcolor = chart.fg_color, size = size.tiny, text_formatting = text.format_bold)
                array.push(drawnLbls, eLb)
                array.push(drawnLbls, sLb)
                array.push(drawnLbls, t1Lb)
                array.push(drawnLbls, t2Lb)
                array.push(drawnLbls, t3Lb)

    // Render top N closest resting BSL pools (above close)
    int bslCount = array.size(bslPools)
    if bslCount > 0
        for i = 0 to bslCount - 1
            LiqPool p = array.get(bslPools, i)
            p.clearPoolDrawings()

        int shownBsl = 0
        for rank = 1 to maxPoolsSide
            int   bestIdx  = -1
            float bestDist = 1e18
            for i = 0 to bslCount - 1
                LiqPool cand = array.get(bslPools, i)
                if na(cand.zoneBox)
                    float d = math.abs(cand.level - close)
                    if d < bestDist
                        bestDist := d
                        bestIdx  := i
            if bestIdx != -1
                LiqPool p = array.get(bslPools, bestIdx)
                if shownBsl == 0
                    nearestBslPrice := p.level
                    nearestBslDef   := p.defPct
                shownBsl += 1
                int xRight = projectedRightTime
                p.zoneBox := box.new(p.pivotTime, p.outerBound, xRight, p.innerBound, xloc = xloc.bar_time, border_color = color.new(bearCol, 55), border_width = 1, bgcolor = color.new(bearCol, 88))
                p.lvlLine := line.new(p.pivotTime, p.level, xRight, p.level, xloc = xloc.bar_time, color = color.new(bearCol, 25), width = 1, style = line.style_dashed)
                string bslTag = "BSL · " + str.tostring(p.level, format.mintick) + " · Seller Def " + str.tostring(p.defPct, "#") + "%"
                p.zoneLbl := label.new(xRight, p.level, bslTag, xloc = xloc.bar_time, yloc = yloc.price, color = color.new(bearCol, 82), style = label.style_label_left, textcolor = chart.fg_color, size = size.tiny)

    // Render top N closest resting SSL pools (below close)
    int sslCount = array.size(sslPools)
    if sslCount > 0
        for i = 0 to sslCount - 1
            LiqPool p = array.get(sslPools, i)
            p.clearPoolDrawings()

        int shownSsl = 0
        for rank = 1 to maxPoolsSide
            int   bestIdx  = -1
            float bestDist = 1e18
            for i = 0 to sslCount - 1
                LiqPool cand = array.get(sslPools, i)
                if na(cand.zoneBox)
                    float d = math.abs(close - cand.level)
                    if d < bestDist
                        bestDist := d
                        bestIdx  := i
            if bestIdx != -1
                LiqPool p = array.get(sslPools, bestIdx)
                if shownSsl == 0
                    nearestSslPrice := p.level
                    nearestSslDef   := p.defPct
                shownSsl += 1
                int xRight = projectedRightTime
                p.zoneBox := box.new(p.pivotTime, p.innerBound, xRight, p.outerBound, xloc = xloc.bar_time, border_color = color.new(bullCol, 55), border_width = 1, bgcolor = color.new(bullCol, 88))
                p.lvlLine := line.new(p.pivotTime, p.level, xRight, p.level, xloc = xloc.bar_time, color = color.new(bullCol, 25), width = 1, style = line.style_dashed)
                string sslTag = "SSL · " + str.tostring(p.level, format.mintick) + " · Buyer Def " + str.tostring(p.defPct, "#") + "%"
                p.zoneLbl := label.new(xRight, p.level, sslTag, xloc = xloc.bar_time, yloc = yloc.price, color = color.new(bullCol, 82), style = label.style_label_left, textcolor = chart.fg_color, size = size.tiny)

// ============================================================================
// 7. SUMMARY DASHBOARD (TOP-RIGHT / BOTTOM-PANEL READY)
// ============================================================================

var table dash = table.new(position.top_right, 3, 9, border_width = 1)

string tblSizeStr = switch dashSizeInput
    DashSize.TINY   => size.tiny
    DashSize.SMALL  => size.small
    DashSize.NORMAL => size.normal
    DashSize.LARGE  => size.large

if barstate.islast and array.size(engines) == 7
    table.clear(dash, 0, 0, 2, 8)
    if showDash
        ProfileEngine winEng = array.get(engines, selectedEngIdx)
        color bgHeader = color.new(chart.bg_color, 8)
        color bgRow    = color.new(chart.bg_color, 18)
        color bdColor  = color.new(chart.fg_color, 82)
        color txtMain  = chart.fg_color
        color txtMuted = color.new(chart.fg_color, 35)

        table.set_bgcolor(dash, bgHeader)
        table.set_border_color(dash, bdColor)

        // Row 0: Header
        string modeStr = hasRealVol ? "LIVE VOL" : "PRICE-PROXY"
        table.cell(dash, 0, 0, "Liquidity Sweep & Targets", text_color = txtMain, bgcolor = bgHeader, text_size = tblSizeStr, text_formatting = text.format_bold, text_halign = text.align_left)
        table.cell(dash, 1, 0, modeStr, text_color = bullCol, bgcolor = bgHeader, text_size = tblSizeStr, text_formatting = text.format_bold)
        table.cell(dash, 2, 0, "[PhenLabs]", text_color = txtMuted, bgcolor = bgHeader, text_size = tblSizeStr)

        // Row 1: Nearest BSL Defense Zone
        string bslInfo = not na(nearestBslPrice) ? (str.tostring(nearestBslPrice, format.mintick) + " (" + str.tostring((nearestBslPrice - close) / safeAtr, "#.1") + " ATR)") : "Scanning..."
        string bslDefS = not na(nearestBslDef) ? ("Seller " + str.tostring(nearestBslDef, "#") + "%") : "—"
        table.cell(dash, 0, 1, "Nearest BSL Pool", text_color = txtMuted, bgcolor = bgRow, text_size = tblSizeStr, text_halign = text.align_left)
        table.cell(dash, 1, 1, bslInfo, text_color = bearCol, bgcolor = bgRow, text_size = tblSizeStr, text_formatting = text.format_bold)
        table.cell(dash, 2, 1, bslDefS, text_color = bearCol, bgcolor = bgRow, text_size = tblSizeStr)

        // Row 2: Nearest SSL Defense Zone
        string sslInfo = not na(nearestSslPrice) ? (str.tostring(nearestSslPrice, format.mintick) + " (" + str.tostring((close - nearestSslPrice) / safeAtr, "#.1") + " ATR)") : "Scanning..."
        string sslDefS = not na(nearestSslDef) ? ("Buyer " + str.tostring(nearestSslDef, "#") + "%") : "—"
        table.cell(dash, 0, 2, "Nearest SSL Pool", text_color = txtMuted, bgcolor = bgRow, text_size = tblSizeStr, text_halign = text.align_left)
        table.cell(dash, 1, 2, sslInfo, text_color = bullCol, bgcolor = bgRow, text_size = tblSizeStr, text_formatting = text.format_bold)
        table.cell(dash, 2, 2, sslDefS, text_color = bullCol, bgcolor = bgRow, text_size = tblSizeStr)

        // Row 3: Active Profile / Auto-Optimizer Winner & Orderflow Delta
        color  deltaCol = netDeltaPct >= 0 ? bullCol : bearCol
        string deltaStr = (netDeltaPct >= 0 ? "Buyer +" : "Seller ") + str.tostring(netDeltaPct, "#.0") + "%"
        string gateStr  = (filterPreset == FilterMode.AUTO_OPTIMIZE ? "Auto Best: " : "Mode: ") + winEng.nameTag
        table.cell(dash, 0, 3, "Active Profile", text_color = txtMuted, bgcolor = bgRow, text_size = tblSizeStr, text_halign = text.align_left)
        table.cell(dash, 1, 3, gateStr, text_color = filterPreset == FilterMode.AUTO_OPTIMIZE ? bullCol : txtMain, bgcolor = bgRow, text_size = tblSizeStr, text_formatting = text.format_bold)
        table.cell(dash, 2, 3, deltaStr, text_color = deltaCol, bgcolor = bgRow, text_size = tblSizeStr, text_formatting = text.format_bold)

        // Row 4: Last Sweep Verdict
        color sigCol = winEng.lastSigDir == 1 ? bullCol : winEng.lastSigDir == -1 ? bearCol : txtMuted
        table.cell(dash, 0, 4, "Last Sweep Signal", text_color = txtMuted, bgcolor = bgRow, text_size = tblSizeStr, text_halign = text.align_left)
        table.cell(dash, 1, 4, winEng.lastSigText, text_color = sigCol, bgcolor = bgRow, text_size = tblSizeStr, text_formatting = text.format_bold)
        table.cell(dash, 2, 4, winEng.lastSigDir != 0 ? "TAKE" : "WAIT", text_color = sigCol, bgcolor = bgRow, text_size = tblSizeStr, text_formatting = text.format_bold)

        // Row 5 & 6: Active Trade Plan Readout
        string planStatusStr = "No Active Plan"
        string planLevelsStr = "—"
        string planProgStr   = "—"
        color  planCol       = txtMuted
        if winEng.activePlanIdx >= 0 and winEng.activePlanIdx < array.size(allPlans)
            SavedPlan lp  = array.get(allPlans, winEng.activePlanIdx)
            planCol       := lp.dir == 1 ? bullCol : bearCol
            planStatusStr := (lp.dir == 1 ? "LONG @ " : "SHORT @ ") + str.tostring(lp.entry, format.mintick)
            planLevelsStr := "SL " + str.tostring(lp.activeSl, format.mintick) + " | TP1 " + str.tostring(lp.tp1, format.mintick)
            planProgStr   := (lp.tp1Hit ? "TP1✓ " : "TP1· ") + (lp.tp2Hit ? "TP2✓ " : "TP2· ") + (lp.tp3Hit ? "TP3✓" : lp.slHit ? "SL✗" : lp.beHit ? "BE✓" : "OPEN")

        table.cell(dash, 0, 5, "Active Trade Plan", text_color = txtMuted, bgcolor = bgRow, text_size = tblSizeStr, text_halign = text.align_left)
        table.cell(dash, 1, 5, planStatusStr, text_color = planCol, bgcolor = bgRow, text_size = tblSizeStr, text_formatting = text.format_bold)
        table.cell(dash, 2, 5, planProgStr, text_color = planCol, bgcolor = bgRow, text_size = tblSizeStr, text_formatting = text.format_bold)

        table.cell(dash, 0, 6, "Plan SL / TP1", text_color = txtMuted, bgcolor = bgRow, text_size = tblSizeStr, text_halign = text.align_left)
        table.cell(dash, 1, 6, planLevelsStr, text_color = txtMain, bgcolor = bgRow, text_size = tblSizeStr)
        string beNote = useBeRatchet ? "BE @ TP1: ON" : "BE @ TP1: OFF"
        table.cell(dash, 2, 6, beNote, text_color = txtMuted, bgcolor = bgRow, text_size = tblSizeStr)

        // Row 7: Closed-Bar Win/Loss Record (Exact for Selected / Auto-Best Profile)
        float winRate  = winEng.totalTrades > 0 ? (float(winEng.winTrades) / float(winEng.totalTrades)) * 100.0 : 0.0
        string wrStr   = winEng.totalTrades > 0 ? (str.tostring(winRate, "#.1") + "% (" + str.tostring(winEng.winTrades) + "W / " + str.tostring(winEng.lossTrades) + "L)") : "Calibrating (0 Trades)"
        string netRStr = (winEng.cumNetR >= 0 ? "+" : "") + str.tostring(winEng.cumNetR, "#.1") + "R"
        color  wrCol   = winRate >= 55.0 ? bullCol : winEng.totalTrades == 0 ? txtMuted : chart.fg_color
        table.cell(dash, 0, 7, "Win Rate (TP1+)", text_color = txtMuted, bgcolor = bgRow, text_size = tblSizeStr, text_halign = text.align_left)
        table.cell(dash, 1, 7, wrStr, text_color = wrCol, bgcolor = bgRow, text_size = tblSizeStr, text_formatting = text.format_bold)
        table.cell(dash, 2, 7, netRStr, text_color = winEng.cumNetR >= 0 ? bullCol : bearCol, bgcolor = bgRow, text_size = tblSizeStr, text_formatting = text.format_bold)

        // Row 8: Target Ladder Hit Breakdown
        string tpHitsStr = "TP1: " + str.tostring(winEng.tp1Count) + " | TP2: " + str.tostring(winEng.tp2Count) + " | TP3: " + str.tostring(winEng.tp3Count)
        table.cell(dash, 0, 8, "Target Hits", text_color = txtMuted, bgcolor = bgRow, text_size = tblSizeStr, text_halign = text.align_left)
        table.cell(dash, 1, 8, tpHitsStr, text_color = txtMain, bgcolor = bgRow, text_size = tblSizeStr)
        table.cell(dash, 2, 8, "Closed-Bar", text_color = txtMuted, bgcolor = bgRow, text_size = tblSizeStr)

// ============================================================================
// 8. PINE SCREENER PLOTS (FIRST 10 PLOTS), VISUAL MARKERS & ALERTS
// ============================================================================

ProfileEngine activeEng = array.size(engines) == 7 ? array.get(engines, selectedEngIdx) : ProfileEngine.new(0, "", 55.0, 1.5, 1.0, 0.35, false, 0.0, 0, 0, 0, 0, 0, 0, 0.0, 0, 0.0, "", -1, false, false, false, false, false, false, false, false)

bool  bullTakeSig    = activeEng.barBullTake
bool  bearTakeSig    = activeEng.barBearTake
bool  bullSkipSig    = activeEng.barBullSkip
bool  bearSkipSig    = activeEng.barBearSkip
bool  tp1HitEvent    = activeEng.barTp1Evt
bool  tp2HitEvent    = activeEng.barTp2Evt
bool  tp3HitEvent    = activeEng.barTp3Evt
bool  slHitEvent     = activeEng.barSlEvt
float barSweepAbsorb = bullTakeSig ? buyerAbsorbVal : bearTakeSig ? sellerAbsorbVal : 0.0

float screenerSignal = bullTakeSig ? 1.0 : bearTakeSig ? -1.0 : 0.0
float activePlanDir  = (activeEng.activePlanIdx >= 0 and activeEng.activePlanIdx < array.size(allPlans)) ? (array.get(allPlans, activeEng.activePlanIdx).status == 0 ? float(array.get(allPlans, activeEng.activePlanIdx).dir) : 0.0) : 0.0
float currentWinRate = activeEng.totalTrades > 0 ? (float(activeEng.winTrades) / float(activeEng.totalTrades)) * 100.0 : 0.0

plot(screenerSignal, "Screener: Sweep Reversal Signal (+1=Buy, -1=Sell)", display = display.data_window + display.pine_screener)
plot(barSweepAbsorb, "Screener: Sweep Absorption % (0-100)", display = display.data_window + display.pine_screener)
plot(netDeltaPct,    "Screener: Orderflow Pressure Delta %", display = display.data_window + display.pine_screener)
plot(activePlanDir,  "Screener: Active Trade Plan (+1=Long, -1=Short)", display = display.data_window + display.pine_screener)
plot(currentWinRate, "Screener: Historical TP1+ Win Rate %", display = display.data_window + display.pine_screener)

// Invisible pane-displayed price anchors so Y-axis stays locked to symbol price when scrolling/zooming
plot(high, "Price Scale High Anchor", color = color.new(color.white, 100), display = display.pane, editable = false)
plot(low,  "Price Scale Low Anchor",  color = color.new(color.white, 100), display = display.pane, editable = false)

// Optional skipped-sweep markers
plotshape(bullSkipSig and showSkipped, title = "Skipped Bullish Sweep", style = shape.circle, location = location.belowbar, color = color.new(bullCol, 55), size = size.tiny, text = "SKIP", textcolor = color.new(chart.fg_color, 40))
plotshape(bearSkipSig and showSkipped, title = "Skipped Bearish Sweep", style = shape.circle, location = location.abovebar, color = color.new(bearCol, 55), size = size.tiny, text = "SKIP", textcolor = color.new(chart.fg_color, 40))

// Alert conditions
alertcondition(bullTakeSig, title = "1. Bullish Liquidity Sweep Reversal (BUY)", message = "LSRT [PhenLabs]: Confirmed Bullish Sell-Side Liquidity (SSL) Sweep Reversal on {{ticker}} ({{interval}}). Entry, SL, and TP1-TP3 trade plan active.")
alertcondition(bearTakeSig, title = "2. Bearish Liquidity Sweep Reversal (SELL)", message = "LSRT [PhenLabs]: Confirmed Bearish Buy-Side Liquidity (BSL) Sweep Reversal on {{ticker}} ({{interval}}). Entry, SL, and TP1-TP3 trade plan active.")
alertcondition(bullTakeSig or bearTakeSig, title = "3. Any Confirmed Liquidity Sweep Reversal (BUY or SELL)", message = "LSRT [PhenLabs]: Confirmed Liquidity Sweep Reversal signal on {{ticker}} ({{interval}}).")
alertcondition(tp1HitEvent, title = "4. Trade Plan TP1 Reached", message = "LSRT [PhenLabs]: Active trade plan reached TP1 (1.0R) on {{ticker}} ({{interval}}).")
alertcondition(tp2HitEvent, title = "5. Trade Plan TP2 Reached", message = "LSRT [PhenLabs]: Active trade plan reached TP2 (2.0R) on {{ticker}} ({{interval}}).")
alertcondition(tp3HitEvent, title = "6. Trade Plan TP3 Reached", message = "LSRT [PhenLabs]: Active trade plan reached TP3 on {{ticker}} ({{interval}}).")
alertcondition(slHitEvent,  title = "7. Trade Plan Stop Loss Hit", message = "LSRT [PhenLabs]: Active trade plan hit Stop Loss on {{ticker}} ({{interval}}).")
